 1.Spark原理之作业执行原理中Shuffle详解下Shuffle Writer
   
   ShuffleWriter（抽象类），有3个具体的实现：
       SortShuffleWriter。sortShulleWriter 需要在 Map 排序
       UnsafeShuffleWriter。使用Java Unsafe直接操作内存，避免Java对象多余的开销和GC 延迟，效率高
	   BypassMergeSortShuffleWriter。和Hash Shuffle的实现基本相同，区别在于map task输出汇总一个文件，同
时还会产生一个index file
   以上 ShuffleWriter 有各自的应用场景。分别如下：
       不满足以下条件使用 SortShuffleWriter
	   没有map端聚合，RDD的partitions分区数小于16,777,216,且Serializer支持relocation【Serializer 可以对已
经序列化的对象进行排序，这种排序起到的效果和先对数据排序再序列化一致】，使用UnsafeShuffleWriter
       没有map端聚合操作 且 RDD的partition分区数小于200个，使用 BypassMergerSortShuffleWriter
   bypass运行机制
   bypass运行机制的触发条件如下：
       shuffle map task数量 <= spark.shuffle.sort.bypassMergeThreshold (缺省200)
	   不是聚合类的shuffle算子
   Bypass机制 Writer 流程如下：
       每个Map Task为每个下游 reduce task 创建一个临时磁盘文件，并将数据按key进行hash然后根据hash值写入
内存缓冲，缓冲写满之后再溢写到磁盘文件；
       最后将所有临时磁盘文件都合并成一个磁盘文件，并创建索引文件；
	   Bypass方式的Shuffle Writer机制与Hash Shuffle是类似的，在shuffle过程中会创建很多磁盘文件，最后多了一
个磁盘文件合并的过程。Shuffle Read的性能会更好；
       Bypass方式与普通的Sort Shuffle方式的不同在于：
	       磁盘写机制不同
           根据key求hash，减少了数据排序操作，提高了性能
   Shuffle Writer 流程
       数据先写入一个内存数据结构中。不同的shuffle算子，可能选用不同的数据结构
	       如果是reduceByKey聚合类的算子，选用 Map 数据结构，一边通过 Map 进行聚合，一边写入内存
           如果是join类的shuffle算子，那么选用 Array 数据结构，直接写入内存
       检查是否达到内存阈值。每写一条数据进入内存数据结构之后，就会判断一下，是否达到了某个临界阈值。如
果达到临界阈值的话，那么就会将内存数据结构中的数据溢写到磁盘，并清空内存数据结构
       数据排序。在溢写到磁盘文件之前，会先根据key对内存数据结构中已有的数据进行排序。排序过后，会分批将
数据写入磁盘文件。默认的batch数量是10000条,也就是说,排序好的数据,会以每批1万条数据的形式分批写入磁盘文件
       数据写入缓冲区。写入磁盘文件是通过Java的 BufferedOutputStream 实现的。BufferedOutputStream是Java
的缓冲输出流，首先会将数据缓冲在内存中,当内存缓冲满溢之后再一次写入磁盘文件中，这样可以减少磁盘IO次数,提
升性能	
       重复写多个临时文件。一个 Task 将所有数据写入内存数据结构的过程中，会发生多次磁盘溢写操作，会产生多
个临时文件
       临时文件合并。最后将所有的临时磁盘文件进行合并，这就是merge过程。此时会将之前所有临时磁盘文件中
的数据读取出来，然后依次写入最终的磁盘文件之中
       写索引文件。由于一个 Task 就只对应一个磁盘文件，也就意味着该task为下游stage的task准备的数据都在这
一个文件中，因此还会单独写一份索引文件,其中标识了下游各个Task的数据在文件中的 start offset与 end offset    
    
 2.Shuffle MapOutputTracker
   
   Spark的shuffle过程分为Writer和Reader：
       Writer负责生成中间数据
       Reader负责整合中间数据
   而中间数据的元信息，则由MapOutputTracker负责管理。 它负责Writer和Reader的沟通。
   Shuffle Writer会将中间数据保存到Block里面，然后将数据的位置发送给MapOutputTracker。
   Shuffle Reader通过向 MapOutputTracker 获取中间数据的位置之后，才能读取到数据。
   
   Shuffle Reader 需要提供 shuffleId、mapId、reduceId 才能确定一个中间数据：
       shuffleId，表示此次shuffle的唯一id
       mapId，表示map端 rdd 的分区索引，表示由哪个父分区产生的数据
       reduceId，表示reduce端的分区索引，表示属于子分区的那部分数据
	   
   MapOutputTracker在executor和driver端都存在：
      MapOutputTrackerMaster 和 MapOutputTrackerMasterEndpoint（负责通信） 存在于driver
      MapOutputTrackerWorker 存在于 executor 端
	  MapOutputTrackerMaster 负责管理所有 shuffleMapTask 的输出数据，每个 shuffleMapTask 执行完后会把
执行结果(MapStatus对象)注册到 MapOutputTrackerMaster
      MapOutputTrackerMaster 会处理executor发送的 GetMapOutputStatuses请求,并返回serializedMapStatus 
给executor端
      MapOutputTrackerWorker 负责为 reduce 任务提供 shuffleMapTask 的输出数据信息（MapStatus对象）
	  如果MapOutputTrackerWorker在本地没有找到请求的 shuffle 的 mapStatus，则会向
MapOutputTrackerMasterEndpoint 发送 GetMapOutputStatuses 请求获取对应的 mapStatus
 
 3.Shuffle Reader
   
   Map Task执行完毕后会将文件位置、计算状态等信息封装到MapStatus对象中,再由本进程中的
MapOutPutTrackerWorker 对象将其发送给Driver进程的 MapOutPutTrackerMaster对象
   Reduce Task开始执行之前会先让本进程中的 MapOutputTrackerWorker 向 Driver 进程中的
MapOutputTrackerMaster 发动请求，获取磁盘文件位置等信息
   当所有的Map Task执行完毕后，Driver进程中的 MapOutputTrackerMaster 就掌握了所有的Shuffle文件的信
息。此时MapOutPutTrackerMaster会告诉MapOutPutTrackerWorker磁盘小文件的位置信息
   完成之前的操作之后，由 BlockTransforService 去 Executor 所在的节点拉数据，默认会启动五个子线程。每次
拉取的数据量不能超过48M
 
 4.Hadoop Shuffle 与 Spark Shuffle 的区别
   
   共同点：
   二者从功能上看是相似的；从High Level来看，没有本质区别，实现（细节）上有区别
   
   实现上的区别：
       Hadoop中有一个Map完成，Reduce便可以去fetch数据了，不必等到所有Map任务完成；而Spark的必须等到父
stage完成，也就是父stage的 map 操作全部完成才能去fetch数据。这是因为spark必须等到父stage执行完,才能执
行子stage，主要是为了迎合stage规则
       Hadoop的Shuffle是sort-base的，那么不管是Map的输出，还是Reduce的输出，都是partition内有序的，而
spark不要求这一点
       Hadoop的Reduce要等到fetch完全部数据，才将数据传入reduce函数进行聚合，而 Spark是一边fetch一边聚
合

 5.Shuffle优化
   
   Spark作业的性能主要就是消耗了shuffle过程，因为该环节包含了众多低效的IO操作：磁盘IO、序列化、网络数据传
输等；如果要让作业的性能更上一层楼，就有必要对 shuffle 过程进行调优。
   但必须注意的是，影响Spark作业性能的因素，主要还是代码质量、资源参数以及数据倾斜，shuffle调优只能在整个
Spark的性能调优中占到一小部分而已。
   开发过程中对 Shuffle 的优化：
       减少Shuffle过程中的数据量
       避免Shuffle
    
	以下介绍 Shuffle 的优化主要是参数优化：
    优化一：调节 map 端缓冲区大小
	    spark.shuffle.file.buffer 默认值为32K，shuffle write阶段buffer缓冲大小。将数据写到磁盘文件之前,会
先写入buffer缓冲区，缓冲写满后才溢写到磁盘
        调节map端缓冲的大小，避免频繁的磁盘IO操作，进而提升任务整体性能
        合理设置参数，性能会有 1%~5% 的提升
    
	优化二：调节 reduce 端拉取数据缓冲区大小
	    spark.reducer.maxSizeInFlight 默认值为48M。设置shuffle read阶段buffer缓冲区大小，这个buffer缓冲决定
了每次能够拉取多少数据
        在内存资源充足的情况下,可适当增加参数的大小(如96m),减少拉取数据的次数及网络传输次数,进而提升性能
		合理设置参数，性能会有 1%~5% 的提升
	
	优化三：调节 reduce 端拉取数据重试次数及等待间隔
        Shuffle read阶段拉取数据时，如果因为网络异常导致拉取失败，会自动进行重试
        spark.shuffle.io.maxRetries，默认值3。最大重试次数
        spark.shuffle.io.retryWait，默认值5s。每次重试拉取数据的等待间隔
        一般调高最大重试次数，不调整时间间隔
    
	优化四：调节 Sort Shuffle 排序操作阈值
	    如果shuffle reduce task的数量小于阈值，则shuffle write过程中不会进行排序操作，而是直接按未经优化的
Hash Shuffle方式写数据，最后将每个task产生的所有临时磁盘文件都合并成一个文件，并创建单独的索引文件
        spark.shuffle.sort.bypassMergeThreshold，默认值为200
        当使用SortShuffleManager时，如果的确不需要排序操作，建议将这个参数调大
	
	优化五：调节 Shuffle 内存大小
	    Spark给 Shuffle 阶段分配了专门的内存区域，这部分内存称为执行内存
		如果内存充足，而且很少使用持久化操作，建议调高这个比例，给 shuffle 聚合操作更多内存，以避免由于内存
不足导致聚合过程中频繁读写磁盘
        合理调节该参数可以将性能提升10%左右